package pl.droidsonroids.gif;

import ohos.aafwk.ability.DataAbilityHelper;
import ohos.aafwk.ability.DataAbilityRemoteException;
import ohos.global.resource.NotExistException;
import ohos.global.resource.RawFileDescriptor;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.utils.Parcel;
import ohos.utils.Sequenceable;
import ohos.utils.net.Uri;

import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.Locale;

/**
 * Lightweight version of {@link GifDrawable} used to retrieve metadata of GIF only,
 * without having to allocate the memory for its pixels.
 */

/**
 * GifAnimationMetaData
 */
public class GifAnimationMetaData implements Serializable, Sequenceable {
    /**
     * createFromParcel
     */
    public static final Producer<GifAnimationMetaData> CREATOR = new Producer<GifAnimationMetaData>() {
        @Override
        public GifAnimationMetaData createFromParcel(Parcel parcel) {
            return new GifAnimationMetaData(parcel);
        }

        public GifAnimationMetaData[] newArray(int size) {
            return new GifAnimationMetaData[size];
        }
    };

    private static final long serialVersionUID = 5692363926580237325L;
    private static final int DEFAULT_FOUR = 4;
    private int mLoopCount;
    private int mDuration;
    private int mHeight;
    private int mWidth;
    private int mImageCount;
    private long mPixelsBytesCount;
    private long mMetadataBytesCount;

    private GifAnimationMetaData(Parcel in) {
        mLoopCount = in.readInt();
        mDuration = in.readInt();
        mHeight = in.readInt();
        mWidth = in.readInt();
        mImageCount = in.readInt();
        mMetadataBytesCount = in.readLong();
        mPixelsBytesCount = in.readLong();
    }

    /**
     * Retrieves from resource.
     *
     * @param res Resources to read from
     * @param id resource id
     * @throws IOException when opening failed
     * @throws NullPointerException if res is null
     * @throws NotExistException NotExistException
     * @throws WrongTypeException WrongTypeException
     */
    public GifAnimationMetaData(ResourceManager res, int id) throws IOException, NotExistException, WrongTypeException {
        this(res.getRawFileEntry(res.getMediaPath(id)).openRawFileDescriptor());
    }

    /**
     * Retrieves metadata from asset.
     *
     * @param assets AssetManager to read from
     * @param assetName name of the asset
     * @throws IOException when opening failed
     * @throws NullPointerException if assets or assetName is null
     */
    public GifAnimationMetaData(RawFileEntry assets, String assetName) throws IOException {
        this(assets.openRawFileDescriptor()); // Need to give the proper path
    }

    /**
     * Constructs metadata from given file path.<br>
     * Only metadata is read, no graphic data is decoded here.
     * In practice can be called from main thread. However it will violate
     * os.StrictMode policy if disk reads detection is enabled.<br>
     *
     * @param filePath path to the GIF file
     * @throws IOException when opening failed
     * @throws NullPointerException if filePath is null
     */
    public GifAnimationMetaData(String filePath) throws IOException {
        this(new GifInfoHandle(filePath));
    }

    /**
     * Equivalent to {@code} GifMetadata(file.getPath())}
     *
     * @param file the GIF file
     * @throws IOException when opening failed
     * @throws NullPointerException if file is null
     */
    public GifAnimationMetaData(File file) throws IOException {
        this(file.getPath());
    }

    /**
     * Retrieves metadata from InputStream.
     * InputStream must support marking, IllegalArgumentException will be thrown otherwise.
     *
     * @param stream stream to read from
     * @throws IOException when opening failed
     * @throws IllegalArgumentException if stream does not support marking
     * @throws NullPointerException if stream is null
     */
    public GifAnimationMetaData(InputStream stream) throws IOException {
        this(new GifInfoHandle(stream));
    }

    /**
     * Retrieves metadata from AssetFileDescriptor.
     *
     * @param afd source
     * @throws NullPointerException if afd is null
     * @throws IOException when opening failed
     */
    public GifAnimationMetaData(RawFileDescriptor afd) throws IOException {
        this(new GifInfoHandle(afd));
    }

    /**
     * Retrieves metadata from FileDescriptor
     *
     * @param fd source
     * @throws IOException when opening failed
     * @throws NullPointerException if fd is null
     */
    public GifAnimationMetaData(FileDescriptor fd) throws IOException {
        this(new GifInfoHandle(fd));
    }

    /**
     * Retrieves metadata from byte array.<br>
     * It can be larger than size of the GIF data. Bytes beyond GIF terminator are not accessed.
     *
     * @param bytes raw GIF bytes
     * @throws IOException if bytes does not contain valid GIF data
     * @throws NullPointerException if bytes are null
     */
    public GifAnimationMetaData(byte[] bytes) throws IOException {
        this(new GifInfoHandle(bytes));
    }

    /**
     * Retrieves metadata from {@link ByteBuffer}. Only direct buffers are supported.
     * Buffer can be larger than size of the GIF data. Bytes beyond GIF terminator are not accessed.
     *
     * @param buffer buffer containing GIF data
     * @throws IOException if buffer does not contain valid GIF data or is indirect
     * @throws NullPointerException if buffer is null
     */
    public GifAnimationMetaData(ByteBuffer buffer) throws IOException {
        this(new GifInfoHandle(buffer));
    }

    /**
     * GifAnimationMetaData
     *
     * @param resolver resolver
     * @param uri uri
     * @throws IOException IOException
     * @throws DataAbilityRemoteException DataAbilityRemoteException
     */
    public GifAnimationMetaData(DataAbilityHelper resolver, Uri uri) throws IOException, DataAbilityRemoteException {
        this(GifInfoHandle.openUri(resolver, uri));
    }

    private GifAnimationMetaData(final GifInfoHandle gifInfoHandle) {
        mLoopCount = gifInfoHandle.getLoopCount();
        mDuration = gifInfoHandle.getDuration();
        mWidth = gifInfoHandle.getWidth();
        mHeight = gifInfoHandle.getHeight();
        mImageCount = gifInfoHandle.getNumberOfFrames();
        mMetadataBytesCount = gifInfoHandle.getMetadataByteCount();
        mPixelsBytesCount = gifInfoHandle.getAllocationByteCount();
        gifInfoHandle.recycle();
    }

    /**
     * getWidth
     *
     * @return width od the GIF canvas in pixels
     */
    public int getWidth() {
        return mWidth;
    }

    /**
     * getHeight
     *
     * @return height od the GIF canvas in pixels
     */
    public int getHeight() {
        return mHeight;
    }

    /**
     * getNumberOfFrames
     *
     * @return number of frames in GIF, at least one
     */
    public int getNumberOfFrames() {
        return mImageCount;
    }

    /**
     * See {@link GifDrawable#getLoopCount()}
     *
     * @return loop count, 0 means that animation is infinite
     */
    public int getLoopCount() {
        return mLoopCount;
    }

    /**
     * See {@link GifDrawable#getDuration()}
     *
     * @return duration of of one loop the animation in milliseconds. Result is always multiple of 10.
     */
    public int getDuration() {
        return mDuration;
    }

    /**
     * getDuration
     *
     * @return true if GIF is animated (has at least 2 frames and positive duration), false otherwise
     */
    public boolean isAnimated() {
        return mImageCount > 1 && mDuration > 0;
    }

    /**
     * Like {@link GifDrawable#getAllocationByteCount()} but does not include memory needed for backing pixel.
     * {@code Bitmap} in {@code GifDrawable} may be allocated at the time of creation or existing one may be reused
     * is used.
     * This method assumes no subsampling (sample size = 1).<br>
     * To calculate allocation byte count of {@link GifDrawable} created from the same input source
     * can be used.
     *
     * @return possible size of the memory needed to store pixels excluding backing pixel and assuming no subsampling
     */
    public long getAllocationByteCount() {
        return mPixelsBytesCount;
    }

    /**
     * Like {@link #getAllocationByteCount()} but includes also backing pixel and takes sample size into account.
     *
     * @param oldDrawable optional old drawable to be reused, pass {@code null} if there is no one
     * @param sampleSize sample size, pass {@code 1} if not using subsampling
     * @return possible size of the memory needed to store pixels
     * @throws IllegalArgumentException if sample size out of range
     */
    public long getDrawableAllocationByteCount(GifDrawable oldDrawable, int sampleSize) {
        if (sampleSize < 1 || sampleSize > Character.MAX_VALUE) {
            throw new IllegalStateException("Sample size " + sampleSize
                    + " out of range <1, " + Character.MAX_VALUE + ">");
        }

        final int sampleSizeFactor = sampleSize * sampleSize;
        final long bufferSize;
        if (oldDrawable != null && oldDrawable.mPixelMap != null) {
            bufferSize = oldDrawable.mPixelMap.getPixelBytesCapacity();
        } else {
            bufferSize = (mWidth * mHeight * DEFAULT_FOUR) / sampleSizeFactor;
        }
        return (mPixelsBytesCount / sampleSizeFactor) + bufferSize;
    }

    /**
     * See{@link GifDrawable#getMetadataAllocationByteCount()}
     *
     * @return maximum possible size of the allocated memory needed to store metadata
     */
    public long getMetadataAllocationByteCount() {
        return mMetadataBytesCount;
    }

    @Override
    public String toString() {
        final String loopCount = mLoopCount == 0 ? "Infinity" : Integer.toString(mLoopCount);
        final String suffix = String.format(Locale.ENGLISH, "GIF: size: %dx%d, frames: %d,"
                + " loops: %s duration: %d", mWidth, mHeight, mImageCount, loopCount, mDuration);
        return isAnimated() ? "Animated " + suffix : suffix ;
    }

    @Override
    public boolean marshalling(Parcel dest) {
        dest.writeInt(mLoopCount);
        dest.writeInt(mDuration);
        dest.writeInt(mHeight);
        dest.writeInt(mWidth);
        dest.writeInt(mImageCount);
        dest.writeLong(mMetadataBytesCount);
        dest.writeLong(mPixelsBytesCount);
        return true;
    }

    @Override
    public boolean unmarshalling(Parcel in) {
        mLoopCount = in.readInt();
        mDuration = in.readInt();
        mHeight = in.readInt();
        mWidth = in.readInt();
        mImageCount = in.readInt();
        mMetadataBytesCount = in.readLong();
        mPixelsBytesCount = in.readLong();
        return true;
    }
}